{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:39:37.505092Z",
     "start_time": "2025-01-20T06:39:28.540647Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:48.622296Z",
     "iopub.status.busy": "2025-01-22T13:30:48.622026Z",
     "iopub.status.idle": "2025-01-22T13:30:50.961713Z",
     "shell.execute_reply": "2025-01-22T13:30:50.961095Z",
     "shell.execute_reply.started": "2025-01-22T13:30:48.622276Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "```shell\n",
    "$ tree -L 2 archive \n",
    "archive\n",
    "├── monkey_labels.txt\n",
    "├── training\n",
    "│   ├── n0\n",
    "│   ├── n1\n",
    "│   ├── n2\n",
    "│   ├── n3\n",
    "│   ├── n4\n",
    "│   ├── n5\n",
    "│   ├── n6\n",
    "│   ├── n7\n",
    "│   ├── n8\n",
    "│   └── n9\n",
    "└── validation\n",
    "    ├── n0\n",
    "    ├── n1\n",
    "    ├── n2\n",
    "    ├── n3\n",
    "    ├── n4\n",
    "    ├── n5\n",
    "    ├── n6\n",
    "    ├── n7\n",
    "    ├── n8\n",
    "    └── n9\n",
    "\n",
    "22 directories, 1 file\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:39:38.752355Z",
     "start_time": "2025-01-20T06:39:37.505092Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:50.963472Z",
     "iopub.status.busy": "2025-01-22T13:30:50.962817Z",
     "iopub.status.idle": "2025-01-22T13:30:52.058615Z",
     "shell.execute_reply": "2025-01-22T13:30:52.058027Z",
     "shell.execute_reply.started": "2025-01-22T13:30:50.963443Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load 1097 images from training dataset\n",
      "load 272 images from validation dataset\n"
     ]
    }
   ],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor, Resize, Compose, ConvertImageDtype, Normalize\n",
    "\n",
    "\n",
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR1 = Path(\"/content/training/\")\n",
    "DATA_DIR2 = Path(\"/content/validation/\")\n",
    "\n",
    "class MonkeyDataset(datasets.ImageFolder):\n",
    "    def __init__(self, mode, transform=None):\n",
    "        if mode == \"train\":\n",
    "            root = DATA_DIR1 / \"training\"\n",
    "        elif mode == \"val\":\n",
    "            root = DATA_DIR2 / \"validation\"\n",
    "        else:\n",
    "            raise ValueError(\"mode should be one of the following: train, val, but got {}\".format(mode))\n",
    "        super().__init__(root, transform)\n",
    "        self.imgs = self.samples\n",
    "        self.targets = [s[1] for s in self.samples]\n",
    "\n",
    "# resnet 要求的，见 https://pytorch.org/vision/stable/models/generated/torchvision.models.resnet50.html\n",
    "img_h, img_w = 224, 224\n",
    "transform = Compose([\n",
    "     Resize((img_h, img_w)),\n",
    "     ToTensor(),\n",
    "     Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),\n",
    "     ConvertImageDtype(torch.float),\n",
    "])\n",
    "\n",
    "\n",
    "train_ds = MonkeyDataset(\"train\", transform=transform)\n",
    "val_ds = MonkeyDataset(\"val\", transform=transform)\n",
    "\n",
    "print(\"load {} images from training dataset\".format(len(train_ds)))\n",
    "print(\"load {} images from validation dataset\".format(len(val_ds)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:39:38.756387Z",
     "start_time": "2025-01-20T06:39:38.752355Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.059603Z",
     "iopub.status.busy": "2025-01-22T13:30:52.059280Z",
     "iopub.status.idle": "2025-01-22T13:30:52.064664Z",
     "shell.execute_reply": "2025-01-22T13:30:52.064109Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.059579Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9'],\n",
       " {'n0': 0,\n",
       "  'n1': 1,\n",
       "  'n2': 2,\n",
       "  'n3': 3,\n",
       "  'n4': 4,\n",
       "  'n5': 5,\n",
       "  'n6': 6,\n",
       "  'n7': 7,\n",
       "  'n8': 8,\n",
       "  'n9': 9})"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据类别\n",
    "train_ds.classes, train_ds.class_to_idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:39:38.775815Z",
     "start_time": "2025-01-20T06:39:38.756387Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.065576Z",
     "iopub.status.busy": "2025-01-22T13:30:52.065334Z",
     "iopub.status.idle": "2025-01-22T13:30:52.076268Z",
     "shell.execute_reply": "2025-01-22T13:30:52.075689Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.065556Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/content/training/training/n0/n0018.jpg 0\n",
      "torch.Size([3, 224, 224]) 0\n"
     ]
    }
   ],
   "source": [
    "# 图片路径 及 标签\n",
    "for fpath, label in train_ds.imgs:\n",
    "    print(fpath, label)\n",
    "    break\n",
    "\n",
    "for img, label in train_ds:\n",
    "    # c, h, w  label\n",
    "    print(img.shape, label)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:39:38.778866Z",
     "start_time": "2025-01-20T06:39:38.775815Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.078409Z",
     "iopub.status.busy": "2025-01-22T13:30:52.077959Z",
     "iopub.status.idle": "2025-01-22T13:30:52.082251Z",
     "shell.execute_reply": "2025-01-22T13:30:52.081753Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.078378Z"
    }
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:39:38.781799Z",
     "start_time": "2025-01-20T06:39:38.778866Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.083008Z",
     "iopub.status.busy": "2025-01-22T13:30:52.082832Z",
     "iopub.status.idle": "2025-01-22T13:30:52.086672Z",
     "shell.execute_reply": "2025-01-22T13:30:52.086164Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.082989Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "from torch.utils.data.dataloader import DataLoader    \n",
    "\n",
    "batch_size = 16\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T02:19:28.364598500Z",
     "start_time": "2024-07-23T02:19:27.176829700Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.087584Z",
     "iopub.status.busy": "2025-01-22T13:30:52.087252Z",
     "iopub.status.idle": "2025-01-22T13:30:52.230083Z",
     "shell.execute_reply": "2025-01-22T13:30:52.229531Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.087564Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([16, 3, 224, 224])\n",
      "torch.Size([16])\n"
     ]
    }
   ],
   "source": [
    "for imgs, labels in train_loader:\n",
    "    print(imgs.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:28:55.526142Z",
     "start_time": "2025-01-20T07:28:55.358597Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.231055Z",
     "iopub.status.busy": "2025-01-22T13:30:52.230756Z",
     "iopub.status.idle": "2025-01-22T13:30:52.236652Z",
     "shell.execute_reply": "2025-01-22T13:30:52.236051Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.231032Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torchvision.models import resnet50\n",
    "\n",
    "\n",
    "# 加载resnet50模型以及预训练权重，冻结除最后一层外所有层的权重，去除最后一层并添加自定义分类层\n",
    "class ResNet50(nn.Module):\n",
    "    def __init__(self, num_classes=10, frozen=True):\n",
    "        super().__init__()\n",
    "        # 下载预训练权重\n",
    "        self.model = resnet50(weights=\"IMAGENET1K_V2\",) # 这里的weights参数可以选择\"IMAGENET1K_V2\"或None，None表示随机初始化\n",
    "        # 冻结前面的层\n",
    "        if frozen:\n",
    "            for param in self.model.parameters():\n",
    "                param.requires_grad = False # 冻结权重\n",
    "        # for param in self.model.layer4.parameters():\n",
    "        #     param.requires_grad = True  # 解冻 layer4\n",
    "        # for name, param in self.model.named_parameters():\n",
    "        #     if name == \"layer4.2.conv3.weight\":\n",
    "        #         param.requires_grad = True  # 解冻该层\n",
    "        # 添加自定义分类层\n",
    "        # print(self.model)\n",
    "        print(self.model.fc.in_features) # 打印resnet50的最后一层的输入通道数\n",
    "        print(self.model.fc.out_features) # 打印resnet50的最后一层的输出通道数 1000\n",
    "        self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)   # 自定义分类层,把resnet50的最后一层改为num_classes个输出\n",
    "        \n",
    "        \n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "\n",
    "\n",
    "# for idx, (key, value) in enumerate(ResNet50().named_parameters()):\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:28:58.912249Z",
     "start_time": "2025-01-20T07:28:58.749737Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:30:52.237473Z",
     "iopub.status.busy": "2025-01-22T13:30:52.237263Z",
     "iopub.status.idle": "2025-01-22T13:31:02.624154Z",
     "shell.execute_reply": "2025-01-22T13:31:02.623472Z",
     "shell.execute_reply.started": "2025-01-22T13:30:52.237451Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Downloading: \"https://download.pytorch.org/models/resnet50-11ad3fa6.pth\" to /root/.cache/torch/hub/checkpoints/resnet50-11ad3fa6.pth\n",
      "100%|██████████| 97.8M/97.8M [00:09<00:00, 11.0MB/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2048\n",
      "1000\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "20490"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model = ResNet50(num_classes=10, frozen=True)\n",
    "def count_parameters(model): #计算模型总参数量\n",
    "    return sum(p.numel() for p in model.parameters() if p.requires_grad)\n",
    "count_parameters(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:08:10.086234Z",
     "start_time": "2025-01-20T07:08:10.083273Z"
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:48:29.601561Z",
     "start_time": "2025-01-20T06:48:29.597356Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.625139Z",
     "iopub.status.busy": "2025-01-22T13:31:02.624856Z",
     "iopub.status.idle": "2025-01-22T13:31:02.630958Z",
     "shell.execute_reply": "2025-01-22T13:31:02.630342Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.625117Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ResNet50(\n",
       "  (model): ResNet(\n",
       "    (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)\n",
       "    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "    (relu): ReLU(inplace=True)\n",
       "    (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)\n",
       "    (layer1): Sequential(\n",
       "      (0): Bottleneck(\n",
       "        (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "        (downsample): Sequential(\n",
       "          (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "          (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        )\n",
       "      )\n",
       "      (1): Bottleneck(\n",
       "        (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (2): Bottleneck(\n",
       "        (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "    )\n",
       "    (layer2): Sequential(\n",
       "      (0): Bottleneck(\n",
       "        (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "        (downsample): Sequential(\n",
       "          (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
       "          (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        )\n",
       "      )\n",
       "      (1): Bottleneck(\n",
       "        (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (2): Bottleneck(\n",
       "        (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (3): Bottleneck(\n",
       "        (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "    )\n",
       "    (layer3): Sequential(\n",
       "      (0): Bottleneck(\n",
       "        (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "        (downsample): Sequential(\n",
       "          (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
       "          (1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        )\n",
       "      )\n",
       "      (1): Bottleneck(\n",
       "        (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (2): Bottleneck(\n",
       "        (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (3): Bottleneck(\n",
       "        (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (4): Bottleneck(\n",
       "        (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (5): Bottleneck(\n",
       "        (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "    )\n",
       "    (layer4): Sequential(\n",
       "      (0): Bottleneck(\n",
       "        (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "        (downsample): Sequential(\n",
       "          (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)\n",
       "          (1): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        )\n",
       "      )\n",
       "      (1): Bottleneck(\n",
       "        (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "      (2): Bottleneck(\n",
       "        (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)\n",
       "        (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)\n",
       "        (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "        (relu): ReLU(inplace=True)\n",
       "      )\n",
       "    )\n",
       "    (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))\n",
       "    (fc): Linear(in_features=2048, out_features=10, bias=True)\n",
       "  )\n",
       ")"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:48:57.859769Z",
     "start_time": "2025-01-20T06:48:57.855987Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.631949Z",
     "iopub.status.busy": "2025-01-22T13:31:02.631610Z",
     "iopub.status.idle": "2025-01-22T13:31:02.636020Z",
     "shell.execute_reply": "2025-01-22T13:31:02.635373Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.631927Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 23528522\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in model.parameters() )\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T06:52:19.752248Z",
     "start_time": "2025-01-20T06:52:19.747379Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.637592Z",
     "iopub.status.busy": "2025-01-22T13:31:02.636977Z",
     "iopub.status.idle": "2025-01-22T13:31:02.644444Z",
     "shell.execute_reply": "2025-01-22T13:31:02.643795Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.637556Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 2048, 1, 1])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m = nn.AdaptiveAvgPool2d(output_size=(1, 1))\n",
    "input = torch.randn(1, 2048, 9, 9)\n",
    "output = m(input)\n",
    "output.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T07:06:55.092794100Z",
     "start_time": "2024-07-23T07:06:55.088043800Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.645481Z",
     "iopub.status.busy": "2025-01-22T13:31:02.645161Z",
     "iopub.status.idle": "2025-01-22T13:31:02.649247Z",
     "shell.execute_reply": "2025-01-22T13:31:02.648702Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.645458Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2359296"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "512*3*3*512"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T03:45:55.270615100Z",
     "start_time": "2024-07-23T03:45:55.261439200Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.652169Z",
     "iopub.status.busy": "2025-01-22T13:31:02.651819Z",
     "iopub.status.idle": "2025-01-22T13:31:02.656474Z",
     "shell.execute_reply": "2025-01-22T13:31:02.655761Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.652144Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1048576"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "512*1*1*2048"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T02:56:07.349270600Z",
     "start_time": "2024-07-23T02:56:07.337722300Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.657611Z",
     "iopub.status.busy": "2025-01-22T13:31:02.657153Z",
     "iopub.status.idle": "2025-01-22T13:31:02.661350Z",
     "shell.execute_reply": "2025-01-22T13:31:02.660787Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.657587Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "32.0"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "512/16"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T02:59:12.386898800Z",
     "start_time": "2024-07-23T02:59:11.605525600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.662416Z",
     "iopub.status.busy": "2025-01-22T13:31:02.662189Z",
     "iopub.status.idle": "2025-01-22T13:31:02.690494Z",
     "shell.execute_reply": "2025-01-22T13:31:02.689834Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.662393Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.691960Z",
     "iopub.status.busy": "2025-01-22T13:31:02.691346Z",
     "iopub.status.idle": "2025-01-22T13:31:02.755828Z",
     "shell.execute_reply": "2025-01-22T13:31:02.755288Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.691936Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T02:59:18.296416Z",
     "start_time": "2024-07-23T02:59:18.287854800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.757132Z",
     "iopub.status.busy": "2025-01-22T13:31:02.756512Z",
     "iopub.status.idle": "2025-01-22T13:31:02.762729Z",
     "shell.execute_reply": "2025-01-22T13:31:02.762096Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.757101Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T02:59:21.080731100Z",
     "start_time": "2024-07-23T02:59:21.073276Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.763557Z",
     "iopub.status.busy": "2025-01-22T13:31:02.763333Z",
     "iopub.status.idle": "2025-01-22T13:31:02.768286Z",
     "shell.execute_reply": "2025-01-22T13:31:02.767774Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.763536Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T03:00:28.829465300Z",
     "start_time": "2024-07-23T02:59:52.601793600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:31:02.769258Z",
     "iopub.status.busy": "2025-01-22T13:31:02.769047Z",
     "iopub.status.idle": "2025-01-22T13:35:24.458905Z",
     "shell.execute_reply": "2025-01-22T13:35:24.458280Z",
     "shell.execute_reply.started": "2025-01-22T13:31:02.769237Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2048\n",
      "1000\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 50%|█████     | 690/1380 [04:21<04:21,  2.64it/s, epoch=9]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 10 / global_step 690\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "model = ResNet50(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 sgd\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.0)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "# tensorboard_callback = TensorBoardCallback(\"runs/monkeys-resnet50\")\n",
    "# tensorboard_callback.draw_model(model, [1, 3, img_h, img_w])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints/monkeys-resnet50\", save_step=len(train_loader), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:36:16.532671Z",
     "iopub.status.busy": "2025-01-22T13:36:16.532295Z",
     "iopub.status.idle": "2025-01-22T13:36:21.760010Z",
     "shell.execute_reply": "2025-01-22T13:36:21.759271Z",
     "shell.execute_reply.started": "2025-01-22T13:36:16.532646Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.cloud.aliyuncs.com/pypi/simple\n",
      "Collecting torchviz\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/5e/06/bea648249802b65282414caf5e7bc94fcb6e5a3e311b537845417d19edb9/torchviz-0.0.3-py3-none-any.whl (5.7 kB)\n",
      "Requirement already satisfied: torch in /usr/local/lib/python3.10/site-packages (from torchviz) (2.5.1)\n",
      "Collecting graphviz (from torchviz)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/00/be/d59db2d1d52697c6adc9eacaf50e8965b6345cc143f671e1ed068818d5cf/graphviz-0.20.3-py3-none-any.whl (47 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m47.1/47.1 kB\u001b[0m \u001b[31m6.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: filelock in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (3.16.1)\n",
      "Requirement already satisfied: typing-extensions>=4.8.0 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (4.12.2)\n",
      "Requirement already satisfied: networkx in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (3.4.2)\n",
      "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (3.1.5)\n",
      "Requirement already satisfied: fsspec in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (2024.6.1)\n",
      "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.4.127)\n",
      "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.4.127)\n",
      "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.4.127)\n",
      "Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (9.1.0.70)\n",
      "Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.4.5.8)\n",
      "Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (11.2.1.3)\n",
      "Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (10.3.5.147)\n",
      "Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (11.6.1.9)\n",
      "Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.3.1.170)\n",
      "Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (2.21.5)\n",
      "Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.4.127)\n",
      "Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (12.4.127)\n",
      "Requirement already satisfied: triton==3.1.0 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (3.1.0)\n",
      "Requirement already satisfied: sympy==1.13.1 in /usr/local/lib/python3.10/site-packages (from torch->torchviz) (1.13.1)\n",
      "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.10/site-packages (from sympy==1.13.1->torch->torchviz) (1.3.0)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/site-packages (from jinja2->torch->torchviz) (2.1.5)\n",
      "Installing collected packages: graphviz, torchviz\n",
      "Successfully installed graphviz-0.20.3 torchviz-0.0.3\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install torchviz"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T03:02:10.300044700Z",
     "start_time": "2024-07-23T03:02:09.443403Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-22T13:36:23.585056Z",
     "iopub.status.busy": "2025-01-22T13:36:23.584650Z",
     "iopub.status.idle": "2025-01-22T13:36:24.021715Z",
     "shell.execute_reply": "2025-01-22T13:36:24.020867Z",
     "shell.execute_reply.started": "2025-01-22T13:36:23.585029Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "ename": "RuntimeError",
     "evalue": "Input type (torch.FloatTensor) and weight type (torch.cuda.FloatTensor) should be the same or input should be a MKLDNN tensor and weight is a dense tensor",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mRuntimeError\u001b[0m                              Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[24], line 11\u001b[0m\n\u001b[1;32m      8\u001b[0m dummy_input \u001b[38;5;241m=\u001b[39m torch\u001b[38;5;241m.\u001b[39mrandn(\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m3\u001b[39m, \u001b[38;5;241m224\u001b[39m, \u001b[38;5;241m224\u001b[39m)  \u001b[38;5;66;03m# Replace with your input shape\u001b[39;00m\n\u001b[1;32m     10\u001b[0m \u001b[38;5;66;03m# Forward pass to generate the computation graph\u001b[39;00m\n\u001b[0;32m---> 11\u001b[0m output \u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdummy_input\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     13\u001b[0m \u001b[38;5;66;03m# Visualize the model architecture\u001b[39;00m\n\u001b[1;32m     14\u001b[0m dot \u001b[38;5;241m=\u001b[39m make_dot(output, params\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mdict\u001b[39m(model\u001b[38;5;241m.\u001b[39mnamed_parameters()))\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/module.py:1736\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1734\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1735\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1736\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/module.py:1747\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1742\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1743\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1744\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1745\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1746\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1747\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1749\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1750\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
      "Cell \u001b[0;32mIn[8], line 27\u001b[0m, in \u001b[0;36mResNet50.forward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m     26\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, x):\n\u001b[0;32m---> 27\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/module.py:1736\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1734\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1735\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1736\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/module.py:1747\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1742\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1743\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1744\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1745\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1746\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1747\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1749\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1750\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torchvision/models/resnet.py:285\u001b[0m, in \u001b[0;36mResNet.forward\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m    284\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, x: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[0;32m--> 285\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_forward_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torchvision/models/resnet.py:268\u001b[0m, in \u001b[0;36mResNet._forward_impl\u001b[0;34m(self, x)\u001b[0m\n\u001b[1;32m    266\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21m_forward_impl\u001b[39m(\u001b[38;5;28mself\u001b[39m, x: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[1;32m    267\u001b[0m     \u001b[38;5;66;03m# See note [TorchScript super()]\u001b[39;00m\n\u001b[0;32m--> 268\u001b[0m     x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconv1\u001b[49m\u001b[43m(\u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    269\u001b[0m     x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mbn1(x)\n\u001b[1;32m    270\u001b[0m     x \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrelu(x)\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/module.py:1736\u001b[0m, in \u001b[0;36mModule._wrapped_call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1734\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_compiled_call_impl(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)  \u001b[38;5;66;03m# type: ignore[misc]\u001b[39;00m\n\u001b[1;32m   1735\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1736\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call_impl\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/module.py:1747\u001b[0m, in \u001b[0;36mModule._call_impl\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m   1742\u001b[0m \u001b[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001b[39;00m\n\u001b[1;32m   1743\u001b[0m \u001b[38;5;66;03m# this function, and just call forward.\u001b[39;00m\n\u001b[1;32m   1744\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m (\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_forward_pre_hooks\n\u001b[1;32m   1745\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_backward_pre_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_backward_hooks\n\u001b[1;32m   1746\u001b[0m         \u001b[38;5;129;01mor\u001b[39;00m _global_forward_hooks \u001b[38;5;129;01mor\u001b[39;00m _global_forward_pre_hooks):\n\u001b[0;32m-> 1747\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mforward_call\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m   1749\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m   1750\u001b[0m called_always_called_hooks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mset\u001b[39m()\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/conv.py:554\u001b[0m, in \u001b[0;36mConv2d.forward\u001b[0;34m(self, input)\u001b[0m\n\u001b[1;32m    553\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mforward\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;28minput\u001b[39m: Tensor) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Tensor:\n\u001b[0;32m--> 554\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_conv_forward\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbias\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m/usr/local/lib/python3.10/site-packages/torch/nn/modules/conv.py:549\u001b[0m, in \u001b[0;36mConv2d._conv_forward\u001b[0;34m(self, input, weight, bias)\u001b[0m\n\u001b[1;32m    537\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpadding_mode \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mzeros\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m    538\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m F\u001b[38;5;241m.\u001b[39mconv2d(\n\u001b[1;32m    539\u001b[0m         F\u001b[38;5;241m.\u001b[39mpad(\n\u001b[1;32m    540\u001b[0m             \u001b[38;5;28minput\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_reversed_padding_repeated_twice, mode\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpadding_mode\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m    547\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgroups,\n\u001b[1;32m    548\u001b[0m     )\n\u001b[0;32m--> 549\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mF\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconv2d\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m    550\u001b[0m \u001b[43m    \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mweight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbias\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstride\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpadding\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdilation\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mgroups\u001b[49m\n\u001b[1;32m    551\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[0;31mRuntimeError\u001b[0m: Input type (torch.FloatTensor) and weight type (torch.cuda.FloatTensor) should be the same or input should be a MKLDNN tensor and weight is a dense tensor"
     ]
    }
   ],
   "source": [
    "# 画图\n",
    "\n",
    "import torch\n",
    "from torchviz import make_dot\n",
    "\n",
    "# Assuming your model is already defined and named 'model'\n",
    "# Construct a dummy input\n",
    "dummy_input = torch.randn(1, 3, 224, 224)  # Replace with your input shape\n",
    "\n",
    "# Forward pass to generate the computation graph\n",
    "output = model(dummy_input)\n",
    "\n",
    "# Visualize the model architecture\n",
    "dot = make_dot(output, params=dict(model.named_parameters()))\n",
    "dot.render(\"model_architecture\", format=\"png\")  # Save the visualization as an image\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:35:48.652263Z",
     "iopub.status.busy": "2025-01-22T13:35:48.651877Z",
     "iopub.status.idle": "2025-01-22T13:35:48.860661Z",
     "shell.execute_reply": "2025-01-22T13:35:48.860064Z",
     "shell.execute_reply.started": "2025-01-22T13:35:48.652237Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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0H344Ycex/Tr14+0tDSeeOKJao9PTk7GaDSSlpbGH//4xybF0JJkSpsQQniZizqGA9A9NqSVIxHN5WSha0qbVHiEEJ4mTZrEF198waJFi5g0aZL79uTkZD755BO2bdvG9u3bufnmm6t1dGuorl27YrPZWLBgAYcOHeLf//43r7/+uscxs2fP5scff+Tuu+9mx44d/PrrryxcuJDc3FwCAwOZNWsWDz74IO+++y4HDx5k48aN/Otf/zqn195cJOERQggvc8+IZFbeeynXpjR8ga3wLdMu68S0nnau6uudHY2EEK3niiuuICIigr1793LzzTe7b583bx7h4eEMGzaMsWPHMmrUKHf1p7FSUlKYN28ezz33HH369GHJkiXMmTPH45hu3brxzTffsH37doYMGUJqaiqffvoper1zgtgjjzzCfffdx6OPPkrPnj256aabyMnJafoLb0YypU0IIbyMv5+O5Bip7rRlCREmepgVSZGNmGIjhLgg6HQ6jh+vvt4oKSmJb7/91uO26dOne3x/6NAhCgoKGvQ89957L/fee6/HbbfccovH95dddhnr16+vNc6HH36Yhx9+uEHP15qkwiOEEEIIIYRosyThEUIIIYQQog1ZsmQJwcHBNX659tK5kMiUNiGEEEIIIdqQa6+9ltTU1Brv8/f3b+FoWp8kPEIIIYQQQrQhISEhhIWFtXYYXkOmtAkhhBBCCCHaLEl4hBBCCCFEm6OUau0QxDk4nz8/SXiEEEIIIUSb4VqjUlxc3MqRiHPh+vmdjzVHsoZHCCGEEEK0GX5+fpjNZvcmmCaTCU3TGnUOh8OB1WqltLQUnc536gNtIW5N0yguLiYnJwez2Yyfn985n18SHiGEEEII0abExsYCuJOexlJKUVJSgtFobHSy1JraUtxms9n9czxXkvAIIYQQQog2RdM04uLiiI6OxmazNfrxNpuN7777jksvvdSn2ji3lbj9/f3PS2XHpUkJz6uvvsoLL7xAVlYWKSkpLFiwgCFDhtR6/H//+18eeeQR0tPTSU5O5rnnnuOqq65qctBCCCGEEELUx8/Pr0kXzn5+fpSXlxMYGOhTiYPEXbNGT+774IMPmDlzJo899hg///wzKSkpjBo1qtaS4Q8//MDEiRO544472Lp1K+PHj2f8+PHs3LnznIMXQgghhBBCiLo0OuGZN28ed955J7fd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ZWVlXh338OHD+eWXX9i2bZv7a9CgQUyaNMn9b2+N/WyFhYUcPHiQuLg4r37PL7744motbfft20diYiLgvZ/PqhYvXkx0dDRXX321+zZvfs8vNL44Nnnz772MTTI2NZWvjEvg+2OTV4xL56PrgrdaunSpCggIUG+//bbavXu3+tOf/qTMZrNHt4fWYLFY1NatW9XWrVsVoObNm6e2bt2qjhw5opRythY0m83q008/VTt27FDjxo2rsbXgRRddpDZt2qS+//57lZyc3KytBadNm6bCwsLUmjVrPFoMFhcXu4+ZOnWqSkhIUN9++6366aefVGpqqkpNTXXf72ovOHLkSLVt2za1YsUK1a5du2Zv6fjQQw+ptWvXqsOHD6sdO3aohx56SGmapr755huvjrsmVTvheHPs9913n1qzZo06fPiwWr9+vRoxYoSKiopSOTk5Xh335s2blV6vV88884zav3+/WrJkiTKZTOq9995zH+ONn08Xu92uEhIS1KxZs6rd563v+YXIG8cmXxyXlJKxyVs+s74wNvnquKSUb49N3jIutemERymlFixYoBISEpTBYFBDhgxRGzdubO2Q1OrVqxVQ7Wvy5MlKKWd7wUceeUTFxMSogIAANXz4cLV3716Pc+Tl5amJEyeq4OBgFRoaqm677TZlsViaLeaa4gXU4sWL3ceUlJSou+++W4WHhyuTyaQmTJigTpw44XGe9PR0NWbMGGU0GlVUVJS67777lM1ma7a4lVLq9ttvV4mJicpgMKh27dqp4cOHuwcUb467JmcPKt4a+0033aTi4uKUwWBQ7du3VzfddJPHfgHeGrdSSn3++eeqT58+KiAgQPXo0UO98cYbHvd74+fT5euvv1ZAtXiU8u73/ELkbWOTL45LSsnY5C2fWV8Ym3x5XFLKd8cmbxmXNKWUalxNSAghhBBCCCF8Q5tdwyOEEEIIIYQQkvAIIYQQQggh2ixJeIQQQgghhBBtliQ8QgghhBBCiDZLEh4hhBBCCCFEmyUJjxBCCCGEEKLNkoRHCCGEEEII0WZJwiOEEEIIIYRosyThEeI8mTJlCuPHj2/tMIQQQghAxiUhXCThEUIIIYQQQrRZkvAI0UgfffQRffv2xWg0EhkZyYgRI3jggQd45513+PTTT9E0DU3TWLNmDQCZmZn87ne/w2w2ExERwbhx40hPT3efz/UXuCeeeIJ27doRGhrK1KlTsVqtrfMChRBC+BQZl4Som761AxDCl5w4cYKJEyfy/PPPM2HCBCwWC+vWrePWW28lIyODgoICFi9eDEBERAQ2m41Ro0aRmprKunXr0Ov1PP3004wePZodO3ZgMBgASEtLIzAwkDVr1pCens5tt91GZGQkzzzzTGu+XCGEEF5OxiUh6icJjxCNcOLECcrLy7nuuutITEwEoG/fvgAYjUbKysqIjY11H//ee+/hcDh466230DQNgMWLF2M2m1mzZg0jR44EwGAwsGjRIkwmE7179+bJJ5/kgQce4KmnnkKnk0KsEEKImsm4JET95DdWiEZISUlh+PDh9O3blxtvvJE333yT06dP13r89u3bOXDgACEhIQQHBxMcHExERASlpaUcPHjQ47wmk8n9fWpqKoWFhWRmZjbr6xFCCOHbZFwSon5S4RGiEfz8/Fi5ciU//PAD33zzDQsWLODhhx9m06ZNNR5fWFjIwIEDWbJkSbX72rVr19zhCiGEaONkXBKifpLwCNFImqZx8cUXc/HFF/Poo4+SmJjIsmXLMBgM2O12j2MHDBjABx98QHR0NKGhobWec/v27ZSUlGA0GgHYuHEjwcHBdOzYsVlfixBCCN8n45IQdZMpbUI0wqZNm3j22Wf56aefyMjI4JNPPuHkyZP07NmTpKQkduzYwd69e8nNzcVmszFp0iSioqIYN24c69at4/Dhw6xZs4a//OUvHD161H1eq9XKHXfcwe7du/nyyy957LHHmDFjhsyTFkIIUScZl4Son1R4hGiE0NBQvvvuO+bPn09BQQGJiYm89NJLjBkzhkGDBrFmzRoGDRpEYWEhq1ev5re//S3fffcds2bN4rrrrsNisdC+fXuGDx/u8Ze14cOHk5yczKWXXkpZWRkTJ07k8ccfb70XKoQQwifIuCRE/TSllGrtIIS4kE2ZMoX8/HyWL1/e2qEIIYQQMi6JNkfqkkIIIYQQQog2SxIeIYQQQgghRJslU9qEEEIIIYQQbZZUeIQQQgghhBBtliQ8QgghhBBCiDZLEh4hhBBCCCFEmyUJjxBCCCGEEKLNkoRHCCGEEEII0WZJwiOEEEIIIYRosyThEUIIIYQQQrRZkvAIIYQQQggh2ixJeIQQQgghhBBt1v8HcdpAqfPiHuMAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:37:05.192569Z",
     "iopub.status.busy": "2025-01-22T13:37:05.192188Z",
     "iopub.status.idle": "2025-01-22T13:37:10.630959Z",
     "shell.execute_reply": "2025-01-22T13:37:10.630459Z",
     "shell.execute_reply.started": "2025-01-22T13:37:05.192543Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.2795\n",
      "accuracy: 0.9853\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/monkeys-resnet50/best.ckpt\",weights_only=True, map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
